package com.cskaoyan.wordmemorize.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cskaoyan.wordmemorize.common.constant.ResultCodeEnum;
import com.cskaoyan.wordmemorize.common.exception.BusinessException;
import com.cskaoyan.wordmemorize.converter.UserCheckinConverter;
import com.cskaoyan.wordmemorize.dao.entity.UserCheckinDO;
import com.cskaoyan.wordmemorize.dao.entity.UserCheckinStatisticsDO;
import com.cskaoyan.wordmemorize.dao.entity.UserDO;
import com.cskaoyan.wordmemorize.dao.mapper.UserCheckinMapper;
import com.cskaoyan.wordmemorize.dao.mapper.UserCheckinStatisticsMapper;
import com.cskaoyan.wordmemorize.dao.mapper.UserMapper;
import com.cskaoyan.wordmemorize.dto.admin.PageDTO;
import com.cskaoyan.wordmemorize.dto.app.UserCheckinDTO;
import com.cskaoyan.wordmemorize.dto.app.UserCheckinStatisticsDTO;
import com.cskaoyan.wordmemorize.request.PageRequest;
import com.cskaoyan.wordmemorize.request.UserCheckinCommand;
import com.cskaoyan.wordmemorize.service.UserCheckinService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;

/**
 * 用户打卡服务实现类
 */
@Service
public class UserCheckinServiceImpl implements UserCheckinService {

    @Resource
    private UserCheckinMapper userCheckinMapper;

    @Resource
    private UserCheckinStatisticsMapper userCheckinStatisticsMapper;

    @Resource
    private UserMapper userMapper;

    @Resource
    private UserCheckinConverter userCheckinConverter;

    // TODO 用户等级变更，签到时实现


    @Override
    public PageDTO<UserCheckinDTO> getCheckInData(PageRequest pageRequest, Long userId) {
        // 构建分页对象的默认参数，默认从第一页开始，每页显示1000条记录
        // '/user/checkin/list?pageNum=1&count=1000' 传过来是这样，所以为保证数据能正常查询，这里也用1000作为默认值
        int pageNum = pageRequest.getPageNum() != null ? pageRequest.getPageNum() : 1;
        int count = pageRequest.getCount() != null ? pageRequest.getCount() : 1000;

        // 创建分页对象
        Page<UserCheckinDO> page = new Page<>(pageNum, count);

        // 构建查询条件
        LambdaQueryWrapper<UserCheckinDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCheckinDO::getUserId, userId);
        queryWrapper.eq(UserCheckinDO::getIsDeleted, 0);

        // 没有关键词，因此就不设置关键词筛选

        //        // 按签到日期降序排序
        //        queryWrapper.orderByDesc(UserCheckinDO::getSignDate);

        // 执行分页查询
        Page<UserCheckinDO> resultPage = userCheckinMapper.selectPage(page, queryWrapper);

        // 转换为DTO并返回
        List<UserCheckinDTO> dtoList = userCheckinConverter.userCheckinDOs2DTOs(resultPage.getRecords());
        return userCheckinConverter.userCheckinPage2PageDTO(dtoList, resultPage.getTotal());
    }

    @Override
    @Transactional
    public UserCheckinStatisticsDTO checkin(UserCheckinCommand command) {
        // 1. 检查是否已经签到
        LambdaQueryWrapper<UserCheckinDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCheckinDO::getUserId, command.getUserId());
        queryWrapper.eq(UserCheckinDO::getSignDate, command.getSignDate());
        queryWrapper.eq(UserCheckinDO::getIsDeleted, 0);

        UserCheckinDO existingCheckin = userCheckinMapper.selectOne(queryWrapper);
        if (existingCheckin != null) {
            throw new BusinessException("今日已签到，请勿重复签到", 6017);
        }

        // 2. 创建签到记录
        UserCheckinDO userCheckinDO = userCheckinConverter.command2DO(command);
        // 签到段号
        userCheckinDO.setStickNo(getNextStickNo(Long.valueOf(command.getUserId())));
        userCheckinMapper.insert(userCheckinDO);

        // 3. 更新签到统计数据

        return updateCheckinStatistics(Long.valueOf(command.getUserId()));
    }

    @Override
    public UserCheckinStatisticsDTO getUserCheckinStatistics(Long userId) {
        // 查询用户签到统计数据
        LambdaQueryWrapper<UserCheckinStatisticsDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCheckinStatisticsDO::getUserId, userId);
        queryWrapper.eq(UserCheckinStatisticsDO::getIsDeleted, 0);

        UserCheckinStatisticsDO statisticsDO = userCheckinStatisticsMapper.selectOne(queryWrapper);

        if (statisticsDO == null) {
            // 如果不存在，创建一个新的统计记录
            statisticsDO = createNewStatistics(userId);
        }

        return userCheckinConverter.statisticsDO2DTO(statisticsDO);
    }


    /**
     * 获取用户连续签到信息
     * 注意：此方法仅返回当前用户的签到统计信息，连续签到天数等数据在签到操作时已更新
     * TODO 具体使用方法待决定，可能只需要使用其中的天数值
     *
     * @param userId 用户ID
     * @return UserCheckinStatisticsDTO 用户签到统计信息
     */
    @Override
    public UserCheckinStatisticsDTO getUserContinuousCheckinInfo(Long userId) {
        // 查询用户签到统计数据
        LambdaQueryWrapper<UserCheckinStatisticsDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCheckinStatisticsDO::getUserId, userId).eq(UserCheckinStatisticsDO::getIsDeleted, 0);

        UserCheckinStatisticsDO statisticsDO = userCheckinStatisticsMapper.selectOne(queryWrapper);

        // 如果不存在统计数据（理论上不会发生，因为用户注册时会初始化）
        if (statisticsDO == null) {
            // 创建新的统计记录
            statisticsDO = createNewStatistics(userId);
        }

        // 转换为DTO并返回
        return userCheckinConverter.statisticsDO2DTO(statisticsDO);
    }


    /**
     * 用户使用补签卡
     *
     * @param userId 用户ID
     *                                                                                                   TODO 实现逻辑待确定
     */
    @Override
    @Transactional
    public void userResignCard(Long userId) {
        // 1. 检查用户是否存在、是否有补签卡
        UserDO userDO = userMapper.selectById(userId);
        if (userDO == null) {
            throw new BusinessException("用户不存在", 500);
        }
        Integer hasSignCard = userDO.getHasSignCard();
        if (hasSignCard == null || hasSignCard <= 0) {
            throw new BusinessException("补签卡数量不足", 500);
        }

        // 2. 检查今天是否已签到（必须先完成当天签到才能补签）
        LocalDate today = LocalDate.now();
        // 构造查询条件，从签到日期记录里查询是否有今日的记录
        LambdaQueryWrapper<UserCheckinDO> todayQuery = new LambdaQueryWrapper<>();
        todayQuery.eq(UserCheckinDO::getUserId, userId);
        todayQuery.eq(UserCheckinDO::getSignDate, today);
        todayQuery.eq(UserCheckinDO::getIsDeleted, 0);
        UserCheckinDO todayCheckin = userCheckinMapper.selectOne(todayQuery);

        // 如果今日没有签到记录，说明今天还没有签到，抛出异常
        if (todayCheckin == null) {
            throw new BusinessException(ResultCodeEnum.USER_CHECKIN_DATA_NEED_SYNC);
        }

        // 3. 获取用户签到统计数据
        LambdaQueryWrapper<UserCheckinStatisticsDO> statsQuery = new LambdaQueryWrapper<>();
        statsQuery.eq(UserCheckinStatisticsDO::getUserId, userId);
        statsQuery.eq(UserCheckinStatisticsDO::getIsDeleted, 0);
        UserCheckinStatisticsDO statistics = userCheckinStatisticsMapper.selectOne(statsQuery);

        if (statistics == null) {
            throw new BusinessException("用户签到统计数据不存在", 500);
        }

        // 4. 检查历史最大连续签到天数是否大于当前连续签到天数
        if (statistics.getMaxContinuousDays() <= statistics.getCurContinuousDays()) {
            throw new BusinessException("当前连续签到已达历史最大，无需补签", 6017);
        }


        // 5. 计算理想补签日期
        // 获取当前连续签到起始日期
        LocalDate curContinuousStartDate = statistics.getCurContinuousStartDate();
        if (curContinuousStartDate == null) {
            throw new BusinessException("当前连续签到起始日期不存在", 500);
        }

        // 理想补签日期为当前连续签到起始日期的前一天
        LocalDate idealResignDate = curContinuousStartDate.minusDays(1);

        // 6. 查询理想补签日期是否已有签到记录
        LambdaQueryWrapper<UserCheckinDO> idealDateQuery = new LambdaQueryWrapper<>();
        idealDateQuery.eq(UserCheckinDO::getUserId, userId);
        idealDateQuery.eq(UserCheckinDO::getSignDate, idealResignDate);
        idealDateQuery.eq(UserCheckinDO::getIsDeleted, 0);

        UserCheckinDO idealDateCheckin = userCheckinMapper.selectOne(idealDateQuery);

        // 获取当前签到段号
        Long currentStickNo = todayCheckin.getStickNo();

        if (idealDateCheckin == null) {
            // 7a. 理想补签日期没有签到记录，直接插入
            UserCheckinDO newCheckin = new UserCheckinDO();
            newCheckin.setUserId(userId);
            newCheckin.setSignDate(idealResignDate);
            newCheckin.setType(2); // 2表示补签
            newCheckin.setStickNo(currentStickNo); // 使用当前签到段号
            userCheckinMapper.insert(newCheckin);
        } else {
            // 7b. 理想补签日期已有签到记录，需要找到该段连续签到的最早日期
            Long existingStickNo = idealDateCheckin.getStickNo();

            // 查找该段连续签到的最早日期
            LambdaQueryWrapper<UserCheckinDO> earliestQuery = new LambdaQueryWrapper<>();
            earliestQuery.eq(UserCheckinDO::getUserId, userId);
            earliestQuery.eq(UserCheckinDO::getStickNo, existingStickNo);
            earliestQuery.eq(UserCheckinDO::getIsDeleted, 0);
            earliestQuery.orderByAsc(UserCheckinDO::getSignDate);
            earliestQuery.last("LIMIT 1");

            UserCheckinDO earliestCheckin = userCheckinMapper.selectOne(earliestQuery);
            if (earliestCheckin != null) {
                // 找到该段连续签到的最早日期的前一天作为新的理想补签日期
                idealResignDate = earliestCheckin.getSignDate().minusDays(1);

                // 插入新的补签记录
                UserCheckinDO newCheckin = new UserCheckinDO();
                newCheckin.setUserId(userId);
                newCheckin.setSignDate(idealResignDate);
                newCheckin.setType(2); // 2表示补签
                newCheckin.setStickNo(currentStickNo); // 使用当前签到段号
                userCheckinMapper.insert(newCheckin);

                // 更新该段所有签到记录的段号为当前段号
                LambdaQueryWrapper<UserCheckinDO> updateQuery = new LambdaQueryWrapper<>();
                updateQuery.eq(UserCheckinDO::getUserId, userId);
                updateQuery.eq(UserCheckinDO::getStickNo, existingStickNo);
                updateQuery.eq(UserCheckinDO::getIsDeleted, 0);

                List<UserCheckinDO> checkinsToUpdate = userCheckinMapper.selectList(updateQuery);
                for (UserCheckinDO checkin : checkinsToUpdate) {
                    checkin.setStickNo(currentStickNo);
                    userCheckinMapper.updateById(checkin);
                }
            }
        }

        // 8. 检查从理想补签日期到当前连续签到起始日期之间的日期是否有签到记录
        LocalDate checkDate = idealResignDate.plusDays(1);
        while (checkDate.isBefore(curContinuousStartDate)) {
            LambdaQueryWrapper<UserCheckinDO> checkQuery = new LambdaQueryWrapper<>();
            checkQuery.eq(UserCheckinDO::getUserId, userId);
            checkQuery.eq(UserCheckinDO::getSignDate, checkDate);
            checkQuery.eq(UserCheckinDO::getIsDeleted, 0);

            UserCheckinDO checkDateCheckin = userCheckinMapper.selectOne(checkQuery);

            if (checkDateCheckin == null) {
                // 没有签到记录，插入补签记录
                UserCheckinDO newCheckin = new UserCheckinDO();
                newCheckin.setUserId(userId);
                newCheckin.setSignDate(checkDate);
                newCheckin.setType(2); // 2表示补签
                newCheckin.setStickNo(currentStickNo); // 使用当前签到段号
                userCheckinMapper.insert(newCheckin);
            } else {
                // 有签到记录，更新段号
                checkDateCheckin.setStickNo(currentStickNo);
                userCheckinMapper.updateById(checkDateCheckin);
            }

            checkDate = checkDate.plusDays(1);
        }

        // 9. 更新用户补签卡数量
        UserDO updateUserDO = new UserDO();
        updateUserDO.setId(userId);
        updateUserDO.setHasSignCard(hasSignCard - 1);
        userMapper.updateById(updateUserDO);

        // 10. 更新签到统计数据
        updateCheckinStatistics(userId);
    }


    /**
     * 获取用户下一个签到段号
     *
     * @param userId 用户ID
     * @return 1L / latestCheckin.getStickNo() + 1 下一个段号
     */
    private Long getNextStickNo(Long userId) {
        // 查找最新的签到记录，按降序，最晚的排最上
        LambdaQueryWrapper<UserCheckinDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCheckinDO::getUserId, userId);
        queryWrapper.eq(UserCheckinDO::getIsDeleted, 0);
        queryWrapper.orderByDesc(UserCheckinDO::getStickNo);
        queryWrapper.last("LIMIT 1"); // 取到最新的一个

        UserCheckinDO latestCheckin = userCheckinMapper.selectOne(queryWrapper);
        // 如果没查到，说明签到记录为空，那么下一个签到段号就从1开始，查到了就加1
        return latestCheckin == null ? 1L : latestCheckin.getStickNo() + 1;
    }

    /**
     * 创建新的统计记录
     *
     * @param userId 用户ID
     * @return UserCheckinStatisticsDO 新的统计记录
     */
    private UserCheckinStatisticsDO createNewStatistics(Long userId) {
        UserCheckinStatisticsDO statisticsDO = new UserCheckinStatisticsDO();
        statisticsDO.setUserId(userId);
        statisticsDO.setTotalDays(0);
        statisticsDO.setMaxContinuousDays(0);
        statisticsDO.setCurContinuousDays(0);
        statisticsDO.setTotalTime(0L);
        userCheckinStatisticsMapper.insert(statisticsDO);
        return statisticsDO;
    }

    /**
     * 更新签到统计数据
     *
     * @param userId 用户ID
     * @return UserCheckinStatisticsDTO 更新后的统计数据
     */
    private UserCheckinStatisticsDTO updateCheckinStatistics(Long userId) {
        // 1. 获取用户所有未删除的签到记录，并按签到日期从早到晚排序
        LambdaQueryWrapper<UserCheckinDO> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserCheckinDO::getUserId, userId);
        queryWrapper.eq(UserCheckinDO::getIsDeleted, 0);
        queryWrapper.orderByAsc(UserCheckinDO::getSignDate);

        List<UserCheckinDO> checkinList = userCheckinMapper.selectList(queryWrapper);

        // 2. 计算总签到天数
        int totalDays = checkinList.size();

        // 3. 计算历史最大连续签到天数，和当前连续签到天数
        int maxContinuousDays = 0;
        int curContinuousDays = 0;
        LocalDate maxContinuousStartDate = null;
        LocalDate curContinuousStartDate = null;

        // =============================== 计算历史最大连续签到天数 ===============================
        // 如果有签到记录
        if (!checkinList.isEmpty()) {
            // 定义一个temp连续天数
            int tempContinuousDays = 1;
            // 定义一个temp起始日期，默认为第一条记录的日期
            LocalDate tempStartDate = checkinList.get(0).getSignDate();

            // 遍历有序签到记录，通过比较相邻日期间隔是否为1天来判断连续性
            for (int i = 1; i < checkinList.size(); i++) {
                // 获取日期，第一次开始遍历时为List的第0条和第1条
                LocalDate prevDate = checkinList.get(i - 1).getSignDate(); // 第0条
                LocalDate currDate = checkinList.get(i).getSignDate(); // 第1条

                // 如果日期连续，temp连续天数+1
                if (ChronoUnit.DAYS.between(prevDate, currDate) == 1) {
                    tempContinuousDays++;
                } else {
                    // 如果日期不连续，说明断签
                    // maxContinuousDays默认为0，如果temp连续天数大于maxContinuousDays，更新maxContinuousDays为当前计算到的最大连续天数
                    if (tempContinuousDays > maxContinuousDays) {
                        maxContinuousDays = tempContinuousDays;
                        maxContinuousStartDate = tempStartDate;
                    }
                    // 如果日期不连续，重新开始计算
                    tempContinuousDays = 1;
                    // 更新temp起始日期
                    tempStartDate = currDate;
                }
            }

            // 因为在上边的逻辑中，只有在断签时才会更新最大连续天数，如果最后一段连续签到记录就是最大的连续天数且刚好没有断签一直连续
            // 那么当遍历到列表末尾时，最后一段连续签到不会被if (tempContinuousDays > maxContinuousDays)判断，导致最大值未被更新
            // 因此在遍历结束后，再单独处理一次最后一段连续签到
            if (tempContinuousDays > maxContinuousDays) {
                maxContinuousDays = tempContinuousDays;
                maxContinuousStartDate = tempStartDate;
            }

            // =============================== 计算当前连续签到天数 ===============================
            // 获取当前日期和前一天日期，用于判断是否属于活跃签到状态，即存不存在当前连续签到
            LocalDate today = LocalDate.now();
            LocalDate yesterday = today.minusDays(1);

            // 获取最后一次签到日期
            UserCheckinDO lastCheckin = checkinList.get(checkinList.size() - 1);

            // 如果最后一次签到是今天或昨天，计算当前连续签到天数
            if (lastCheckin.getSignDate().equals(today) || lastCheckin.getSignDate().equals(yesterday)) {
                curContinuousDays = 1; // 当前连续签到天数
                curContinuousStartDate = lastCheckin.getSignDate(); // 当前连续签到起始日期，为今天/昨天

                // 向前查找连续签到，如果一直连续，当前连续签到天数++，如果有间隔，则为断签，结束
                for (int i = checkinList.size() - 2; i >= 0; i--) {
                    if (ChronoUnit.DAYS.between(checkinList.get(i).getSignDate(), checkinList.get(i + 1).getSignDate()) == 1) {
                        curContinuousDays++;
                        curContinuousStartDate = checkinList.get(i).getSignDate();
                    } else {
                        break;
                    }
                }
            }
        }

        // 4. 更新统计数据
        LambdaQueryWrapper<UserCheckinStatisticsDO> statsQuery = new LambdaQueryWrapper<>();
        statsQuery.eq(UserCheckinStatisticsDO::getUserId, userId);
        statsQuery.eq(UserCheckinStatisticsDO::getIsDeleted, 0);

        // 查询用户签到数据
        UserCheckinStatisticsDO statisticsDO = userCheckinStatisticsMapper.selectOne(statsQuery);
        // 设置数据为刚刚计算好的
        statisticsDO.setTotalDays(totalDays);
        statisticsDO.setMaxContinuousDays(maxContinuousDays);
        statisticsDO.setCurContinuousDays(curContinuousDays);
        statisticsDO.setMaxContinuousStartDate(maxContinuousStartDate);
        statisticsDO.setCurContinuousStartDate(curContinuousStartDate);
        // 学习时长需要另外计算，因此更新已有用户数据时不碰学习时长，避免被覆盖

        // 更新
        userCheckinStatisticsMapper.updateById(statisticsDO);


        return userCheckinConverter.statisticsDO2DTO(statisticsDO);
    }
}
